package filial;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.rmi.RemoteException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutionException;

import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.WindowConstants;

import bank.LockException;
import bank.Logger;

public class SwingClient {

  private final static Color GREEN = new Color(0, 185, 0);
  private final static Color RED = new Color(200, 0, 0);
  private final static int SCHRITTE  = 20000; 

  private final FilialController controller = new FilialController();
  private final KontenComboboxModel kontenCboModel = new KontenComboboxModel(new ArrayList<String>());
  private final KontenComboboxModel kontenUeberweisungCboModel = new KontenComboboxModel(new ArrayList<String>());
  private final JComboBox cboListKonten = new JComboBox();
  private final JLabel lblKontostand = new JLabel();
  private final JButton btNeuesKonto = new JButton("Neues Konto anlegen");
  private final JButton btEinzahlen = new JButton("Einzahlen");
  private final JButton btAbheben = new JButton("Abheben");
  private final JButton btUeberweisen = new JButton("Ueberweisen");
  private final JButton btUberweisen20000 = new JButton("Ueberweisen " + SCHRITTE);
  private final JComboBox cboUeberweiseTo = new JComboBox();
  private final JLabel lblStatus = new JLabel("Status");
  private final JFormattedTextField txtTansferBetrag = new JFormattedTextField(NumberFormat.getInstance(Locale.US));
  private final JFormattedTextField btEinzahlenAbhebenBetrag = new JFormattedTextField(NumberFormat.getInstance(Locale.US));
  private final JProgressBar pbUperwesungen2000 = new JProgressBar(0, SCHRITTE);
  private final JFrame frame;

  
  public SwingClient() {

    pbUperwesungen2000.setValue(0);
    pbUperwesungen2000.setStringPainted(true);

    frame = new JFrame();
    // frame.setIconImage(VSP3Icons.ICON_KERBEROS_MIT);
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.setTitle("Bank Filiale");
    JPanel pane = new JPanel();
    pane.setLayout(new GridBagLayout());
    frame.add(pane);

    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.HORIZONTAL;
    c.insets = new Insets(2, 2, 2, 2);

    c.weightx = 3;
    c.gridx = 0;
    c.gridy = 0;
    c.gridwidth = 3;
    pane.add(btNeuesKonto, c);

    c.weightx = 2;
    c.gridx = 0;
    c.gridy = 1;
    c.gridwidth = 2;
    cboListKonten.setModel(kontenCboModel);
    pane.add(cboListKonten, c);

    c.weightx = 1;
    c.gridx = 2;
    c.gridy = 1;
    c.gridwidth = 1;

    pane.add(lblKontostand, c);

    c.weightx = 1;
    c.gridx = 0;
    c.gridy = 2;
    c.gridwidth = 1;

    pane.add(btEinzahlenAbhebenBetrag, c);

    c.weightx = 1;
    c.gridx = 1;
    c.gridy = 2;
    c.gridwidth = 1;

    pane.add(btEinzahlen, c);

    c.weightx = 1;
    c.gridx = 2;
    c.gridy = 2;
    c.gridwidth = 1;

    pane.add(btAbheben, c);

    JPanel ueberweisungPanel = new JPanel();
    ueberweisungPanel.setLayout(new GridBagLayout());
    ueberweisungPanel.setBorder(BorderFactory.createTitledBorder("Ueberweisung"));

    c.weightx = 3;
    c.gridx = 0;
    c.gridy = 3;
    c.gridwidth = 3;
    pane.add(ueberweisungPanel, c);

    c.weightx = 1;
    c.gridx = 0;
    c.gridy = 0;
    c.gridwidth = 1;
    ueberweisungPanel.add(new JLabel("Betrag"), c);

    c.weightx = 2;
    c.gridx = 1;
    c.gridy = 0;
    c.gridwidth = 2;
    ueberweisungPanel.add(new JLabel("Empfaenger"), c);

    c.weightx = 1;
    c.gridx = 0;
    c.gridy = 2;
    c.gridwidth = 1;
    ueberweisungPanel.add(txtTansferBetrag, c);

    c.weightx = 2;
    c.gridx = 1;
    c.gridy = 2;
    c.gridwidth = 2;
    cboUeberweiseTo.setModel(kontenUeberweisungCboModel);
    ueberweisungPanel.add(cboUeberweiseTo, c);

    c.weightx = 1;
    c.gridx = 0;
    c.gridy = 3;
    c.gridwidth = 1;

    ueberweisungPanel.add(btUeberweisen, c);

    c.weightx = 2;
    c.gridx = 1;
    c.gridy = 3;
    c.gridwidth = 2;

    ueberweisungPanel.add(btUberweisen20000, c);

    c.weightx = 3;
    c.gridx = 0;
    c.gridy = 4;
    c.gridwidth = 3;

    pane.add(lblStatus, c);

    c.weightx = 3;
    c.gridx = 0;
    c.gridy = 5;
    c.gridwidth = 3;

    pane.add(pbUperwesungen2000, c);

    setListeners();

    frame.pack();
    frame.setVisible(true);
    Dimension size = new Dimension(500, 300);
    
    frame.setPreferredSize(size);
    frame.setMinimumSize(size);    
    frame.setSize(size);
    
    frame.setResizable(true);

    updateAccountLists();

    if (kontenCboModel.getSize() > 0) {
      kontenCboModel.setSelectedItem(kontenCboModel.getElementAt(0));
    }

  }

  private void updateAccountLists() {
    List<String> accountList = controller.getAccounts();
    kontenCboModel.update(accountList);
    kontenUeberweisungCboModel.update(accountList);
  }

  private void updateAccountLists(String newAccount) {
    kontenCboModel.addAccount(newAccount);
    kontenUeberweisungCboModel.addAccount(newAccount);
  }

  private void setListeners() {
    cboListKonten.addItemListener(new ItemListener() {

      @Override
      public void itemStateChanged(ItemEvent e) {
        updateBalanceLabel();
      }
    });

    btNeuesKonto.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent e) {
        createAccount();
      }
    });

    btUberweisen20000.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent e) {
        enableGui(false);
        UberweisungsWorker worker = new UberweisungsWorker();
        worker.execute();
      }
    });

    btAbheben.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent e) {
        abheben();
      }
    });

    btEinzahlen.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent e) {
        einzahlen();
      }
    });

    btUeberweisen.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent e) {
        ueberweisen();

      }
    });

  }

  private void enableGui(boolean enabled) {
    btAbheben.setEnabled(enabled);
    btEinzahlen.setEnabled(enabled);
    btNeuesKonto.setEnabled(enabled);
    btUeberweisen.setEnabled(enabled);
    btUberweisen20000.setEnabled(enabled);
    btEinzahlenAbhebenBetrag.setEnabled(enabled);
    txtTansferBetrag.setEnabled(enabled);
    cboListKonten.setEnabled(enabled);
    cboUeberweiseTo.setEnabled(enabled);

    if (enabled) {
      frame.setCursor(Cursor.getDefaultCursor());
    } else {
      frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    }
  }

  private void updateBalanceLabel() {

    String kontoid = String.valueOf(cboListKonten.getSelectedItem());

    if (kontoid != null && !kontoid.isEmpty()) {
      try {
        double kontostand = controller.kontoStand(kontoid);
        setBetrag(kontostand);
      } catch (Exception e1) {
        error(e1);
      }
    }

  }

  private void createAccount() {
    try {
      String newKonto = controller.kontoEinrichten();
      updateAccountLists(newKonto);
      kontenCboModel.setSelectedItem(newKonto);
      setGreenStatus("Created " + newKonto);
    } catch (RemoteException e) {
      error(e);
    }
  }

  private void abheben() {
    pbUperwesungen2000.setValue(0);
    Number betrag = (Number) btEinzahlenAbhebenBetrag.getValue();

    Object konto = cboListKonten.getSelectedItem();

    if (konto == null) {
      setRedStatus("Kein Konto angegeben");
    }

    if (betrag == null) {
      setRedStatus("Kein Betrag angegeben");
    }

    if (betrag != null && konto != null) {
      try {
        double newBetrag = controller.abheben(konto.toString(), betrag.doubleValue());
        setBetrag(newBetrag);
      } catch (Exception e) {
        error(e);
      }
    }
    pbUperwesungen2000.setValue(pbUperwesungen2000.getMaximum());
  }

  private void einzahlen() {
    pbUperwesungen2000.setValue(0);
    Number betrag = (Number) btEinzahlenAbhebenBetrag.getValue();

    Object konto = cboListKonten.getSelectedItem();

    if (konto == null) {
      setRedStatus("Kein Konto angegeben");
    }

    if (betrag == null) {
      setRedStatus("Kein Betrag angegeben");
    }

    if (betrag != null && konto != null) {
      try {
        double newBetrag = controller.einzahlen(konto.toString(), betrag.doubleValue());
        setBetrag(newBetrag);
      } catch (Exception e) {
        error(e);
      }
    }
    pbUperwesungen2000.setValue(pbUperwesungen2000.getMaximum());
  }

  private void ueberweisen() {

    pbUperwesungen2000.setValue(0);
    Number betrag = (Number) txtTansferBetrag.getValue();

    Object from = cboListKonten.getSelectedItem();
    Object to = cboUeberweiseTo.getSelectedItem();

    if (from == null) {
      setRedStatus("Kein Absender angegeben");
    }

    if (to == null) {
      setRedStatus("Kein Empfaenger angegeben");
    }

    if (betrag == null) {
      setRedStatus("Kein Betrag angegeben");
    }

    if (betrag != null && from != null && to != null) {
      try {
        double valueTransfer = betrag.doubleValue();
        setGreenStatus("Ueberweise " + valueTransfer);
        double newBetrag = controller.ueberweisen(valueTransfer, from.toString(), to.toString());
        setBetrag(newBetrag);
      } catch (Exception e) {
        error(e);
      }
    }

    pbUperwesungen2000.setValue(pbUperwesungen2000.getMaximum());
  }

  private void error(Throwable e) {

    setRedStatus(getCause(e).getMessage());
  }

  private Throwable getCause(Throwable e) {

    while (e.getCause() != null) {
      e = e.getCause();
    }

    return e;
  }

  private void setGreenStatus(String status) {
    lblStatus.setText(status);
    lblStatus.setForeground(GREEN);
  }

  private void setRedStatus(String status) {
    lblStatus.setText(status);
    lblStatus.setForeground(RED);
  }

  private void setBetrag(double betrag) {

    DecimalFormat df = new DecimalFormat("########.##");
    lblKontostand.setText("€ " + df.format(betrag));
  }

  class UberweisungsWorker extends SwingWorker<Void, Void> {

    @Override
    protected Void doInBackground() throws Exception {

      Number betrag = (Number) txtTansferBetrag.getValue();

      Object from = cboListKonten.getSelectedItem();
      Object to = cboUeberweiseTo.getSelectedItem();

      if (from == null) {
        setRedStatus("Kein Absender angegeben");
      }

      if (to == null) {
        setRedStatus("Kein Empfaenger angegeben");
      }

      if (betrag == null) {
        setRedStatus("Kein Betrag angegeben");
      }

      if (betrag != null && from != null && to != null) {
        double valueTransfer = betrag.doubleValue();
        setGreenStatus("Ueberweise " + SCHRITTE +  " x " + valueTransfer);
        for (int i = 0; i < SCHRITTE; i++) {

          boolean success = false;
          
          while(!success) {
            try {
              double newBetrag = controller.ueberweisen(valueTransfer, from.toString(), to.toString());
              setBetrag(newBetrag);
              pbUperwesungen2000.setValue(i);
              success = true;
            } catch (RemoteException e) {
              if(getCause(e) instanceof LockException) {
                if(Logger.isDebug()) {
	               Logger.log(String.valueOf(getCause(e)));	
                }
              } else {
                
                throw e;
              }
            }
          }
          
        }
      }

      return null;
    }

    @Override
    protected void done() {

      try {
        get();
      } catch (ExecutionException ex) {
        error(ex.getCause());
      } catch (Throwable e) {
        e.printStackTrace();
        error(e);
      }

      super.done();

      pbUperwesungen2000.setValue(SCHRITTE);

      updateBalanceLabel();

      enableGui(true);
    }
  }

  @SuppressWarnings("serial")
  class KontenComboboxModel extends DefaultComboBoxModel {
    String[] kontoList;

    public KontenComboboxModel(List<String> iList) {
      kontoList = iList.toArray(new String[0]);
    }

    @Override
    public int getSize() {
      return kontoList.length;
    }

    @Override
    public Object getElementAt(int i) {
      return kontoList[i];
    }

    public void addAccount(String newAccount) {
      final Object oldSelection = getSelectedItem();
      final List<String> items = new ArrayList<String>(Arrays.asList(kontoList));
      items.add(newAccount);
      update(items);
      setSelectedItem(oldSelection);

    }

    public void update(List<String> iList) {
      fireIntervalRemoved(this, 0, getSize());
      kontoList = iList.toArray(new String[0]);
      fireIntervalAdded(this, 0, getSize());
    }
  }

  public static void main(String[] args) {

    try {
      for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
          UIManager.setLookAndFeel(info.getClassName());
          break;
        }
      }
    } catch (Exception e) {
      // If Nimbus is not available, you can set the GUI to another look and feel.
    }

    new SwingClient();
  }

}
